![]() |
![]() |
|
Der Benutzer besorgt sich nach der Instanziierung der Klasse Flugzeug die Referenz auf das interne Triebwerksobjekt und kann darauf die beiden Methoden Anlassen und Ausschalten aufrufen:
liefert die Referenz auf das Triebwerk, über die mit der üblichen Punktnotation auf alle Klassenmitglieder des internen Objekts zugegriffen werden kann. Festzustellen ist, dass dem Aufrufer nicht verborgen bleibt, dass ein zweites, internes Objekt der übergeordneten Klasse seine Dienste bereitstellt. 6.5.2 Verbergen des internen Objekts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Class Flugzeug |
| Private myMotor As Triebwerk |
| Public Sub MotorStarten() |
| myMotor.Starten() |
| End Sub |
| Public Sub MotorAusschalten() |
| myMotor.Ausschalten() |
| End Sub |
| ' weiterer Klassencode |
| End Class |
Der Aufruf einer der beiden Instanzmethoden MotorStarten oder MotorAusschalten des Flugzeug-Objekts bewirkt, dass das Triebwerk das gewünschte Verhalten zeigt: Es wird angelassen oder abgeschaltet. Im Gegensatz zur Weiterleitung der internen Objektreferenz bemerkt der Aufrufer nicht, dass das Flugzeug-Objekt sich hinterlistig der Funktionalität einer zweiten Klasse bedient. Setzen wir voraus, dass die Codeimplementierung der beiden Methoden in der untergeordneten Klasse Triebwerk möglicherweise sehr komplex ist, gaukelt diese Lösung dem Aufrufer Intelligenz vor und verbirgt dabei den tatsächlichen Dienstanbieter – ganz im Gegensatz zu der zuerst vorgestellten Variante, bei der die Referenz des Triebwerk-Objekts dem Aufrufer bekannt ist.
Fassen wir an dieser Stelle den Programmcode, der die beiden Möglichkeiten der Implementierung einer Hat-eine-Beziehung beschreibt, zusammen. Die Klasse Flugzeug im folgenden Beispiel bietet Alternativen an, die in der Main-Methode auch beide benutzt werden. Dabei bedient sich das Objekt myCessna der weitergeleiteten Triebwerksreferenz, das Objekt myPiper hingegen ruft die Methoden MotorStarten und MotorAusschalten auf, ohne direkt das interne Objekt vom Typ Triebwerk anzusprechen.
| ' ------------------------------------------------------------ |
| ' Beispiel: ...\Kapitel 6\HatEineBeziehung |
| ' ------------------------------------------------------------ |
| Module Module1 |
| Sub Main() |
| Dim myCessna As Flugzeug = New Flugzeug(8, 4, 80, 150, 300) |
| Console.WriteLine("Der Typ Cessna:") |
| myCessna.Motor.Starten() |
| myCessna.Motor.Ausschalten() |
| Console.WriteLine("------------------------------") |
| Dim myPiper As Flugzeug = New Flugzeug(9, 3, 75, 180, 250) |
| Console.WriteLine("Der Typ Piper:") |
| myPiper.MotorStarten() |
| myPiper.MotorAusschalten() |
| Console.ReadLine() |
| End Sub |
| End Module |
| ' --------- Klasse 'Flugzeug' ------------- |
| Class Flugzeug |
| Private myMotor As Triebwerk |
| Private spannweite As Single |
| Private anzahlDerSitzplaetze As Integer |
| Private minGeschwindigkeit As Single |
| Public Sub New(ByVal spannweite As Single, _ |
| ByVal sitzplaetze As Integer, _ |
| ByVal minGeschwindigkeit As Single, _ |
| ByVal leistung As Integer, _ |
| ByVal gewicht As Single) |
| Me.spannweite = spannweite |
| Me.anzahlDerSitzplaetze = sitzplaetze |
| Me.minGeschwindigkeit = minGeschwindigkeit |
| myMotor = New Triebwerk(leistung, gewicht) |
| End Sub |
| Public Sub MotorStarten() |
| myMotor.Starten() |
| End Sub |
| Public Sub MotorAusschalten() |
| myMotor.Ausschalten() |
| End Sub |
| Public ReadOnly Property Motor() As Triebwerk |
| Get |
| Return myMotor |
| End Get |
| End Property |
| End Class |
| ' --------- Klasse 'Triebwerk' ----------------- |
| Class Triebwerk |
| Private leistung As Integer |
| Private gewicht As Single |
| Public Sub New(ByVal leistung As Integer, ByVal gewicht As Single) |
| Me.leistung = leistung |
| Me.gewicht = gewicht |
| End Sub |
| ' das Triebwerk starten |
| Public Sub Starten() |
| Console.WriteLine("Das Triebwerk wird gestartet") |
| End Sub |
| ' das Triebwerk ausschalten |
| Public Sub Ausschalten() |
| Console.WriteLine("Das Triebwerk wird abgeschaltet") |
| End Sub |
| End Class |
Es gibt noch eine andere Möglichkeit, Hat-eine-Beziehungen zu realisieren. Dazu wird im Gültigkeitsbereich einer Klasse eine weitere Klasse definiert:
| Public Class ClassA |
| Public Class ClassB |
| ' weiterer Code der Klasse ClassB |
| End Class |
| ' weiterer Code der Klasse ClassA |
| End Class |
Eine Klasse, die innerhalb einer anderen Klasse definiert ist, wird als innere Klasse bezeichnet. Im Codefragment ist das die Definition der Klasse ClassB in ClassA. Die Verwendung innerer Klassen ist eine bequeme Möglichkeit zur Bereitstellung untergeordneter Objekte und wird benutzt, wenn ein Typ einem anderen Typ logisch zugeordnet werden kann.
Rufen Sie sich noch einmal das Beispiel des Flugzeugs und des Triebwerks in Erinnerung, deren Klassen wir weiter oben folgendermaßen definiert hatten:
| Class Flugzeug |
| End Class |
| Class Triebwerk |
| End Class |
Ein Triebwerk ist Bauteilkomponente eines Flugzeugs. Wenn kein Zwang dazu besteht, ein Triebwerk auch als separates Objekt einer Betrachtung zu unterziehen, wäre es überlegenswert, die Definition Triebwerk in der Definition Flugzeug einzuschließen:
| Public Class Flugzeug |
| Public Class Triebwerk |
| ' Anweisungen |
| End Class |
| End Class |
Ein Objekt vom Typ Triebwerk ist jetzt abhängig von einem Objekt des Typs Flugzeug und kann nur im Kontext der äußeren Klasse, also von Flugzeug, benutzt werden.
Ist die innere Klasse Public deklariert, muss bei der Instanziierung immer der Typ der äußeren Klasse angegeben werden.
| Dim turbine As Flugzeug.Triebwerk = _ |
| New Flugzeug.Triebwerk(545, 320) |
Die eindeutige Zugehörigkeit eines Triebwerk-Objekts zu einem Flugzeug zeigt sich erst, wenn das Flugzeug-Objekt ein ihm zugeordnetes Triebwerk hat. Dazu wird im Konstruktor der äußeren Klasse die innere Klasse instanziiert und die Referenz in einer gekapselten Variablen vorgehalten.
| Public Class Flugzeug |
| Private turbine As Triebwerk |
| Public Sub New() |
| turbine = New Triebwerk() |
| ' Anweisungen |
| End Sub |
| Public Class Triebwerk |
| ' Anweisungen |
| End Class |
| End Class |
Festhalten können wir, dass sich im Vergleich zu zwei separaten Klassendefinitionen, wie wir sie anfangs hatten, nicht wesentlich viel geändert. Interessant wird es, wenn die innere Klasse Private deklariert wird:
| Public Class Flugzeug |
| ... |
| Private Class Triebwerk |
| ' Anweisungen |
| End Class |
| End Class |
Die Klasse Triebwerk ist nun ein eindeutig einem Flugzeugobjekt zugeordnetes Element. Kein anderer Code, außer der in der äußeren Klasse, hat Zugriff auf den inneren Typ.
Doch wie kann ein Aufrufer ein Triebwerk starten, wenn der Typ Triebwerk für ihn völlig unsichtbar bleibt und er deshalb nicht die klassenspezifischen Methoden aufrufen kann? Die Lösung führt wieder über Methoden, die in der äußeren Klasse Flugzeug definiert sind:
| Public Class Flugzeug |
| Private turbine As Triebwerk |
| Public Sub TurbineStarten() |
| turbine.Starten() |
| End Sub |
| ... |
| ' innere Klasse |
| Private Class Triebwerk |
| ' das Triebwerk starten |
| Public Sub Starten() |
| Console.WriteLine("Das Triebwerk wird gestartet") |
| End Sub |
| ... |
| End Class |
| End Class |
Weil die Klasse Triebwerk Private deklariert ist, hat sie eine Sichtbarkeit wie jede andere private Entität der äußeren Flugzeug-Klasse. Ein Flugzeug-Objekt kann deshalb auch die innere, private Klasse instanziieren. Der resultierende Verweis wird in der Methode TurbineStarten zum Aufruf der Methode Starten benutzt. Obwohl der Aufrufer nichts von der Existenz der Klasse Triebwerk weiß, wird er Nutzen aus dieser Klasse ziehen können und die Reaktion auf den Aufruf zu spüren bekommen – und sei es nur die Ausgabe im Konsolenfenster.
Wir wollen nun unsere Überlegungen hinsichtlich der Abhängigkeit der beiden Klassen Flugzeug und Triebwerk auf die Spitze treiben. Wenn wir es ganz streng sehen, müssen wir feststellen, dass ein Flugzeug ein Triebwerk besitzt und ein Triebwerk auch in einem ganz bestimmten Flugzeug eingebaut ist. Woher weiß das Triebwerk, in welchem Flugzeug es eingebaut ist? Es hat keine Information darüber, die diese Zuordnung beschreibt.
Um den Kreis zu schließen und Eindeutigkeit zu gewährleisten, müssen wir dem Konstruktoraufruf in der Klasse Triebwerk die Referenz auf das Flugzeug-Objekt übergeben, dem das Triebwerk zugeordnet ist. Diese wird im privaten Feld Flugzeug der Klasse Triebwerk gespeichert. Ferner ist in der überarbeiteten Klasse die Methode TriebwerkStarten definiert, aus der heraus zu Demonstrationszwecken die Methode Starten des Flugzeugobjekts aufgerufen wird.
Sehen wir uns nun die Klassendefinition von Flugzeug an, die sich auf das Wesentliche beschränkt. Damit wir uns später im Testcode vom Erfolg überzeugen können, wird ein Flugzeug durch einen Namen beschrieben, der dem Konstruktor der Klasse Flugzeug bei der Instanziierung übergeben wird. Außerdem wird die Eigenschaft Motor mit der Rückgabe der Referenz auf das Triebwerksobjekt angeboten, über die ein Benutzer Zugriff auf die Methoden des Triebwerks hat.
| Public Class Flugzeug |
| Private trmotor As Triebwerk |
| Private name As String |
| ' Konstruktor |
| Public Sub New(ByVal bezeichnung As String) |
| trmotor = New Triebwerk(Me) |
| Me.name = bezeichnung |
| End Sub |
| ' Eigenschaft, welche die Referenz auf das interne |
| ' Triebwerksobjekt liefert |
| Public ReadOnly Property Motor() As Triebwerk |
| Get |
| Return Me.trmotor |
| End Get |
| End Property |
| Public Sub Starten() |
| Console.WriteLine("Das Flugzeug {0} startet", Me.name) |
| End Sub |
| ' ------------ innere Klasse Triebwerk --------------- |
| Public Class Triebwerk |
| Private flugzeug As Flugzeug |
| ' Konstruktor |
| Public Sub New(ByVal flg As Flugzeug) |
| Me.flugzeug = flg |
| Me.flugzeug.trmotor = Me |
| End Sub |
| Public Sub TriebwerkStarten() |
| Console.WriteLine("Das Triebwerk wird gestartet") |
| flugzeug.Starten() |
| End Sub |
| End Class |
| End Class |
Wir können nun die Klasse Flugzeug instanziieren und auf dessen Referenz die Eigenschaft Motor aufrufen, die ihrerseits die Referenz auf das dem Flugzeug zugeordnete Triebwerk liefert. Damit können wir das Triebwerk starten:
| Dim flg As Flugzeug = New Flugzeug("Piper") |
| flg.Motor.TriebwerkStarten() |
An der Konsole erhalten wir daraufhin die Ausgabe:
| Das Triebwerk wird gestartet |
| Das Flugzeug Piper startet |
Wir haben auch die Möglichkeit, die innere Klasse Triebwerk zu instanziieren. Dabei müssen wir dem Konstruktor die Referenz auf ein konkretes Flugzeug-Objekt übergeben:
| Dim flg As Flugzeug = New Flugzeug("Piper") |
| Dim trb As Flugzeug.Triebwerk = New Flugzeug.Triebwerk(flg) |
Grundsätzlich birgt dieser Aufruf die Gefahr der Inkonsistenz, denn wir erzeugen ein neues Triebwerk auf Basis des konkreten Flugzeugs flg. Dieses Flugzeugobjekt ist aber bereits im Besitz eines Triebwerks, das im Konstruktor erzeugt worden ist und dessen Referenz im Feld trmotor vorgehalten wird. Wir müssen dem Flugzeugobjekt mitteilen, dass das erste Triebwerk »ausgetauscht« und durch ein neues ersetzt wird. Um dem Flugzeug die Existenz des neuen Triebwerks mitzuteilen, enthält der Konstruktor der Klasse Triebwerk die Anweisung:
| Me.flugzeug.trmotor = Me |
Nun ist der Kreis vollständig geschlossen. Jedes Triebwerk ist unzweifelhaft einem bestimmten Flugzeug zugeordnet, und jedes Flugzeug hat auch nur ein bestimmtes Triebwerk.
Wie Sie oben gesehen haben, unterscheidet sich eine öffentlich (Public oder Friend) deklarierte innere Klasse nicht wesentlich von einer separaten Klassendefinition. Ein Benutzer »sieht« die innere Klasse und kann sie instanziieren. Das Konzept der verschachtelten Klassen, nämlich die konkrete Zugehörigkeit zu einem Objekt der umschließenden Klasse, wird aufgeweicht.
Ganz anders verhält sich eine Private deklarierte innere Klasse. Die Sichtbarkeit ist nur auf das umschließende äußere Objekt begrenzt, die Bindung ist eindeutig und kann nicht aufgebrochen werden. Erst jetzt kann der Verbund zwischen dem vom Aufrufer erkennbaren Objekt und seinem zugeordneten Teilobjekt die konzeptuellen Stärken voll ausspielen.
Ist die umschließende äußere Klasse nicht NotInheritable definiert, kann sie abgeleitet werden. Normalerweise dürfte die ableitende Klasse gleichermaßen Interesse an einem Objekt der eingebetteten Klasse haben. Weil sich Private deklarierte Komponenten jedoch der Sichtbarkeit der ableitenden Klasse entziehen, sollte die eingebettete Klasse in einer ableitbaren äußeren immer Friend deklariert sein.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.